#include "ProcessManager.h"
#include "GameObject.h"
#include "component/GameComponent.h"
#include "Messenger.h"
#include "event/Events.h"
#include "rendering/ISceneNode.h"
#include "Types.h"
#include "Timer.h"
#include "ConfigurationManager.h"
#include "physics/NvidiaPhysX.h"
#include "rendering/Scene.h"
#include "physics/Physics.h"




using namespace Boon;
using namespace Rendering;
using namespace Physics;



void Boon::ProcessManager::OnInitialize()
{
	m_Scene = Shared_ptr<Scene>( new Scene() );
	m_Scene->Init();
	m_Physics = Shared_ptr<IPhysics>( new NvidiaPhysX() );
	m_Physics->Init();
	Physics::__SetGlobalPhysicsInstance( m_Physics );

	m_Input = &Input::GetInstance();
	// register events
	m_Messenger = &Messenger::GetInstance();
	EventListener listener( this, &ProcessManager::OnGameObjectDirty );
	m_Messenger->Register( Event_GameObject_NewComponent::ID, listener );
	
}

void Boon::ProcessManager::OnDestroy()
{
	// calling dtors of all game objects and components
	m_GameObjectsDirty.clear();
	GameObject::m_GameObjects.clear();
	m_Scene->Unload();
	m_Physics->Unload();

	// unregister events
	EventListener listener( this, &ProcessManager::OnGameObjectDirty );
	m_Messenger->Remove( Event_GameObject_NewComponent::ID, listener );
	
	// release singletons ( prevent calling / reinit of singletons in Dtors )
	Timer::GetInstance().Release();
	ConfigurationManager::GetInstance().Release();
	m_Messenger->Release();
	m_Messenger = nullptr;

	m_Input->Release();
	m_Input = nullptr;

	if( GameObject::m_CreationCallbacks )
		delete GameObject::m_CreationCallbacks;
}

void Boon::ProcessManager::ProcessUpdate()
{
	auto iterNewGO = GameObject::m_NewCreated.begin();
	auto iterNewGOend = GameObject::m_NewCreated.end();
	while( iterNewGO != iterNewGOend )
	{
		GameObject::m_GameObjects[iterNewGO->first] = iterNewGO->second;
		++iterNewGO;
	}
	GameObject::m_NewCreated.clear();

	//Todo: put update blocks to separate functions
	m_Input->Update();

	//Pre Update-------------------------------->
	Timer& timer = Timer::GetInstance();
	timer.CalculateDeltaTime();

	// dequeue messages before updating logic
	m_Messenger->ProcessQueuedEvents();

	// Call ProcessDirty for startup routines of game components
	auto iterDirty = m_GameObjectsDirty.begin();
	auto endDirty = m_GameObjectsDirty.end();

	while( iterDirty != endDirty )
	{
		auto cur = iterDirty;
		++iterDirty;

		if( !(*cur).Expired() && // hasn't been destroyed
			(*cur).Lock()->ProcessDirty() ) // allowed to remove from list
		{
			m_GameObjectsDirty.erase( cur );
		}
	}

	// Update --------------------------------------->
	auto goI = GameObject::m_GameObjects.begin();
	auto goEnd = GameObject::m_GameObjects.end();
	
	for( ; goI != goEnd; ++goI )
		goI->second->Update();

	// Process physics update
	ProcessPhysics();

	// update scene nodes, Animations etc.
	m_Scene->Update();

	// Late Update ------------------------------------>
	for( goI = GameObject::m_GameObjects.begin(); goI != goEnd; ++goI )
		goI->second->LateUpdate();

}

void Boon::ProcessManager::ProcessRendering()
{
	if( Timer::GetInstance().CalculateDeltaTimeRendering() )
	{
		m_Messenger->ProcessQueuedEventsSceneNode();
		m_Scene->OnRender();
	}
}

void Boon::ProcessManager::ProcessPhysics()
{
	uint_32 steps = Timer::GetInstance().CalculateDeltaTimeFixed();
	// process physics steps/sub steps
	while( steps )
	{
		m_Physics->Update();
		--steps;
	}
}

void Boon::ProcessManager::DestroyGameObject( const GameObjectID id )
{
	GameObject::m_GameObjects.erase( id );
}

void Boon::ProcessManager::OnGameObjectDirty( const IMessengerEventStrong& e )
{
	EventGameObjectBase* event = static_cast<EventGameObjectBase*>( e.Get() );
	auto result = GameObject::m_GameObjects.find( event->actorID );
	if( result != GameObject::m_GameObjects.end() )
	{
		// only if not already in list!
		if( result->second->m_DirtyComponents.size() == 0 )
			m_GameObjectsDirty.push_back( result->second );
		return;
	}
	result = GameObject::m_NewCreated.find( event->actorID );
	if( result != GameObject::m_NewCreated.end() )
	{
		// only if not already in list!
		if( result->second->m_DirtyComponents.size() == 0 )
			m_GameObjectsDirty.push_back( result->second );
	}
	else
		std::cout << "das is aber was falsch gangen!" << std::endl;
}


